package poker;

import java.util.ArrayList;
import java.util.List;

import enums.ActionEnum;
import enums.ActionType;
import enums.PlayersLeft;
import enums.State;
import enums.TablePosition;

import poker.ai.Context;
import poker.ai.OpponentModeler;
import poker.gui.GUI_Table;


public class GameRound
{
	private GUI_Table table;

	private int pot;
	private int currentBet;

	private Player dealer;
	/*
	 * The position of the dealer in the player list(zero-based)
	 */
	private int dealerPosition;

	private List<Player> players;
	private Player currentPlayer;
	private int folded;
	private int lastRaisedPointer = -1;

	private Deck deck;

	private Card[] flop;
	private Card turnCard;
	private Card riverCard;
	private Card[] communityCards;

	private boolean isPreFlop;
	private boolean turnDealt;
	private boolean riverDealt;
	private ArrayList<Action> playerActions;
	
	private int rounds = 0;

	private boolean roundOver = false;

	private boolean show = false;

	//Getters
	/**
	 * For testing purposes
	 * @param playerActions
	 */
	public void setPlayerActions(ArrayList<Action> playerActions) { this.playerActions = playerActions; }

	public ArrayList<Action> getPlayerActions() { return playerActions; }
	public int getPot() { return pot; }
	public int getCurrentBet() { return currentBet; }
	public Player getDealer() { return dealer; }
	public int getDealerPosition() { return dealerPosition; }
	public List<Player> getPlayers() { return players; }
	public Player getCurrentPlayer() { return currentPlayer; }
	private State state;

	public int getPlayersLeft() { return players.size() - folded; }

	public Card[] getFlop() { return flop; }
	public Card getTurnCard() { return turnCard; }
	public Card getRiverCard() { return riverCard; }

	public int getLastRaisedPointer() { return lastRaisedPointer; }
	public int getFolded() { return this.folded; }

	public boolean getIsPreFlop() { return isPreFlop; }
	public boolean getTurnDealt(){ return turnDealt;}
	public boolean getRiverDealt(){ return riverDealt;}
	public boolean getFlopDealt(){ return !isPreFlop;}

	public State getState(){
		return this.state;
	}
	
	public int getRounds() { return this.rounds; }


	public int getPosition(Player player)
	{
		for (int i = 0; i < players.size(); i++) 
		{
			if(players.get(i) == player)
				return i;
		}
		return -1;
	}

	public int getPositionRelativeToDealer(Player player)
	{
		return getPosition(player) - getDealerPosition() % players.size();
	}

	public int getSmallBlindPosition()
	{
		return (dealerPosition + 1) % players.size();
	}

	public int getBigBlindPosition()
	{
		return (dealerPosition + 2) % players.size();
	}

	public void showGUI()
	{
		this.show = true;
		table = new GUI_Table(this, show);
		table.setVisible(true);
	}

	public GameRound(ArrayList<Player> players, boolean show)
	{
		this.show = show;
		this.players = players;
		currentPlayer = players.get(0);
		OpponentModeler.getInstance().setGameRound(this);	
	}

	public void start()
	{
		rounds++;
		reset();

		Printer.printStartInformation(
				getDealer(), 
				getPlayers().get(getSmallBlindPosition()), 
				getPlayers().get(getBigBlindPosition()));

		Rules.SetGameRound(this);
		postBlinds();
		dealPocketCards();

		this.state = State.preflop;
		takeBets();
		if(!roundOver)
		{
			dealFlop();
			this.state = State.flop;
		}
		if(!roundOver)
			takeBets();
		if(!roundOver)
		{
			dealTurn();
			this.state = State.turn;
		}
		if(!roundOver)
			takeBets();
		if(!roundOver)
		{
			dealRiver();
			this.state = State.river;
		}
		if(!roundOver)
			takeBets();
		if(!roundOver)
			finishRound();

		if(table != null){
			Logger.getInstance().Save();
			table.queryNewRound();

		}
	}

	private void reset()
	{
		playerActions = new ArrayList<Action>();
		flop = new Card[3];

		dealerPosition = (dealerPosition + 1) % players.size();
		dealer = this.players.get(dealerPosition);
		deck = new Deck();
		flop = new Card[3];
		riverCard = null;
		turnCard = null;
		pot = 0;
		folded = 0;
		isPreFlop = true;
		turnDealt = false;
		riverDealt = false;
		roundOver = false;
		currentPlayer = players.get(dealerPosition);//???

		resetPlayers();
		resetRaiseCounter();
		updateGUI();
	}

	private void resetPlayers() 
	{
		for (Player player : players) 
		{
			player.setFolded(false);
			player.resetCards();
		}
	}

	private void resetRaiseCounter() {
		for (int i = 0; i < players.size(); i++) 
		{ 
			players.get(i).bettingRoundCounter = 2;	
			players.get(i).setPostedCash(0);
		}
	}

	private void postBlinds()
	{
		//Small blind
		postCash(50, players.get(getSmallBlindPosition()));

		//Big blind
		postCash(100, players.get(getBigBlindPosition()));
		currentBet = 100;
	}

	private void postCash(int sum, Player player)
	{
		pot += sum;
		player.setPostedCash(player.getPostedCash() + sum);
		player.addCash(-sum);
	}

	public void updateGUI()
	{
		if(show)
			table.updateGUI(this);
	}


	public void dealPocketCards()
	{	
		int startPosition = dealerPosition + 1;
		for (int i = startPosition; i < (players.size()*2)+startPosition; i++)
		{
			players.get(i % players.size()).setPocketCards(deck.getNextCard());	
		}

		updateGUI();

		Printer.printPocketCards(players);
	}

	public void dealFlop()
	{
		isPreFlop = false;

		Card flopCard1 = deck.getNextCard();
		flop[0] = flopCard1;
		Card flopCard2 = deck.getNextCard();
		flop[1] = flopCard2;
		Card flopCard3 = deck.getNextCard();
		flop[2] = flopCard3;

		updateGUI();
		Printer.printPlayersLeft(players);
		Printer.printFlop(flop);
	}

	public void dealTurn()
	{
		turnDealt = true;
		turnCard = deck.getNextCard();

		Printer.printPlayersLeft(players);
		Printer.printTurn(turnCard);

		updateGUI();
	}

	public void dealRiver()
	{
		riverDealt = true;
		riverCard = deck.getNextCard();

		Printer.printPlayersLeft(players);
		Printer.printRiver(riverCard);

		updateGUI();
	}

	public Card[] getCommunityCards()
	{
		int size = 0;

		if(getFlopDealt())
			size = 3;
		if(turnDealt)
			size++;
		if(riverDealt)
			size++;
		Card[] cards = new Card[size];

		if(getFlopDealt())
		{
			cards[0] = flop[0];
			cards[1] = flop[1];
			cards[2] = flop[2];
		}

		if(getTurnDealt()){ cards[3] = turnCard;}
		if(getRiverDealt()){ cards[4] = riverCard;}
		return cards;
	}

	public Card[] getHand(Player player)
	{
		int size = 2;

		if(getFlopDealt())
			size = 5;

		if(turnDealt)
			size++;
		if(riverDealt)
			size++;
		Card[] cards = new Card[size];

		cards[0] = player.getPocketCards()[0];
		cards[1] = player.getPocketCards()[1];

		if(getFlopDealt())
		{
			cards[2] = flop[0];
			cards[3] = flop[1];
			cards[4] = flop[2];
		}

		if(getTurnDealt()){ cards[5] = turnCard;}
		if(getRiverDealt()){ cards[6] = riverCard;}
		return cards;
	}

	private void takeBets() 
	{
		lastRaisedPointer = -1;
		if(!isPreFlop)
		{
			currentBet = 0;
			resetRaiseCounter();
		}

		playerActions.clear();

		int currentPlayerPos = 0;

		if(isPreFlop) 
		{
			currentPlayerPos = (dealerPosition + 3) % players.size();
		}
		else
			currentPlayerPos = (dealerPosition + 1) % players.size();

		while(!Rules.isBettingRoundOver() && !roundOver)
		{
			for(int i = 0; i < players.size(); i++)
			{
				currentPlayer = players.get(currentPlayerPos);
				if(!currentPlayer.getFolded()) 
				{
					Action lastAction = currentPlayer.doAction(this);
					playerActions.add(lastAction);

					OpponentModeler.getInstance().addContext(currentPlayerPos, 
							new Context(getState(), 
									PlayersLeft.get(getPlayersLeft()), 
									getPlayersLeft(), 
									TablePosition.get(getPositionRelativeToDealer(currentPlayer)), 
									ActionEnum.get(lastAction.getActionType())));

					if(lastAction.getActionType() == ActionType.RAISE || lastAction.getActionType() == ActionType.BET)
					{
						currentPlayer.bettingRoundCounter--; 
						lastRaisedPointer = playerActions.size() - 1;
						currentBet += 100;
					}

					if(lastAction.getActionType() == ActionType.CALL || 
							lastAction.getActionType() == ActionType.BET || 
							lastAction.getActionType() == ActionType.RAISE)
					{
						postCash(lastAction.getSum(), currentPlayer);
					}

					updateGUI();

					if(lastAction.getActionType() == ActionType.FOLD)
						folded++;

					//if there is only one player left
					if(players.size() - folded == 1)
					{
						finishRound();
						break;
					}

					if(Rules.isBettingRoundOver()) {
						break;
					}
				}
				currentPlayerPos = (currentPlayerPos + 1) % players.size();
			}
		}
	}

	private void finishRound() 
	{		
		roundOver = true;

		ArrayList<Player> winners = new ArrayList<Player>();
		for (Player player : players) {
			if(player.getFolded())
				continue;

			OpponentModeler.getInstance().addOpponentStatistics(player);
			
			if(winners.isEmpty()) {
				winners.add(player);
				continue;
			}

			int compare = HandPowerRating.compare(getHand(player), getHand(winners.get(0)));
			if(compare == 1) {
				winners.clear();
				winners.add(player);
			}
			else if(compare == 0)
				winners.add(player);
		}

		for (Player player : winners) 
		{
			player.addCash(pot / winners.size());
		}

		if(players.size() - 1 != folded)
		{
			Printer.printWinner(winners, this, true);
		}
		else
			Printer.printWinner(winners, this, false);

		for (int i = 0; i < players.size(); i++) {
			if(winners.contains(players.get(i))) { players.get(i).setTotalWinsThisGame(1); }
		}
	}
}
